package model;

import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.CRC32;

public class Solution {
	static int id = 0;
	public int identity;

	Problem problem;
	SolverEnvironment env;
	int M;
	int P;
	int R;
	int S;

	int[] processes;
	// M X P
	// int[][] machinesByProcesses;
	// M X R
	int[][] usage;

	public long load_cost_max_contribution_value = -1L;
	public int load_cost_max_contribution_resource = -1;
	public long balance_cost_max_contribution_value = -1L;
	// M X R
	public long[][] load_cost_contribution_unzipped;
	// M
	public long[] load_cost_contribution_zipped;

	// M X R
	public long[][] balance_cost_contribution_unzipped;
	// M
	public long[] balance_cost_contribution_zipped;

	// List<Integer>[] processesListForMachines;

	// problem.S * problem.L size+1
	// the last position count the number of different locations per service
	public int[][] serviceProcessesPerLocation;

	// problem.S * problem.N size+1
	// the last position count the number of different neighborhoods per service
	public int[][] serviceProcessesPerNeighborhood;

	// problem.M * problem.TRE.length
	long[][] transientResourceUsageOfMachine;

	boolean isLightCopy = false;

	long[] costComponents;
	long cost;

	public Solution(SolverEnvironment env) {
		this.env = env;
		init(env.getProblem());
	}

	public Solution(Problem problem) {
		init(problem);
	}

	@SuppressWarnings("unchecked")
	private void init(Problem problem) {
		this.identity = ++id;
		this.problem = problem;
		M = problem.getMachines().size();
		P = problem.getProcesses().size();
		R = problem.getResources().size();
		S = problem.getServices().size();
		processes = new int[P];
		for (int i = 0; i < P; i++) {
			processes[i] = -1;
		}
		// machinesByProcesses = new int[M][P];
		// processesListForMachines = new List[M];
		// for (int i = 0; i < M; i++) {
		// processesListForMachines[i] = new ArrayList<Integer>();
		// }
		usage = new int[M][R];
		load_cost_contribution_unzipped = new long[M][R];
		load_cost_contribution_zipped = new long[M];
		balance_cost_contribution_unzipped = new long[M][R];
		balance_cost_contribution_zipped = new long[M];

		serviceProcessesPerLocation = new int[problem.S][problem.L + 1];
		serviceProcessesPerNeighborhood = new int[problem.S][problem.N + 1];
		transientResourceUsageOfMachine = new long[problem.M][problem.TRE.length];
	}

	public final int getUsageOfMachineForResource(int m_index, int r_index) {
		return usage[m_index][r_index];
	}

	public void assignProcessToMachine(int p_index, int m_index) {
		processes[p_index] = m_index;
		// machinesByProcesses[m_index][p_index] = 1;
		// processesListForMachines[m_index].add(p_index);
		for (int j = 0; j < R; j++) {
			usage[m_index][j] += problem.R[p_index][j];
		}
		updateServiceProcessesDistributionAdd(p_index, m_index);
		if (env != null)
			updateTransientResourceUsageOfMachine(env.getInitialSolution(),
					p_index, -1, m_index);
	}

	public void removeProcess(int p_index) {
		int previous_m_index = processes[p_index];
		processes[p_index] = -1;
		// machinesByProcesses[previous_m_index][p_index] = 0;
		// processesListForMachines[previous_m_index].remove(new
		// Integer(p_index));
		for (int j = 0; j < R; j++) {
			usage[previous_m_index][j] -= problem.R[p_index][j];
		}
		updateServiceProcessesDistributionRemove(p_index, previous_m_index);
		if (env != null)
			updateTransientResourceUsageOfMachine(env.getInitialSolution(),
					p_index, previous_m_index, -1);
	}

	public void moveProcess(int p_index, int previous_m_index, int new_m_index) {
		processes[p_index] = new_m_index;
		int r = 0;
		for (int j = 0; j < R; j++) {
			r = problem.R[p_index][j];
			usage[previous_m_index][j] -= r;
			usage[new_m_index][j] += r;
		}
		updateServiceProcessesDistribution(p_index, previous_m_index,
				new_m_index);
		updateTransientResourceUsageOfMachine(env.getInitialSolution(),
				p_index, previous_m_index, new_m_index);
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		List<Integer>[] processesListForMachines = new List[M];
		for (int i = 0; i < M; i++) {
			processesListForMachines[i] = new ArrayList<Integer>();
		}
		for (int p = 0; p < problem.P; p++) {
			processesListForMachines[processes[p]].add(p);
		}
		for (int m_i = 0; m_i < processesListForMachines.length; m_i++) {
			Collections.sort(processesListForMachines[m_i]);
			sb.append(String.format("Machine %d --> %s\n", m_i,
					processesListForMachines[m_i]));
		}
		return sb.toString();
	}

	public Integer getMachineIdForProcessId(int p_id) {
		return processes[p_id];
	}

	public List<Integer> getProcessesForMachine(int machineId) {
		List<Integer> processesList = new ArrayList<Integer>();
		for (int i = 0; i < P; i++) {
			if (processes[i] == machineId)
				processesList.add(i);
		}
		return processesList;
	}

	public List<Integer> getProcessesForNeighborhood(int neighborhood_id) {
		List<Integer> processesList = new ArrayList<Integer>();
		for (Integer m_id : problem
				.getMachinesIdsForNeighborhoodId(neighborhood_id)) {
			processesList.addAll(getProcessesForMachine(m_id));
		}
		return processesList;
	}

	public int getNeighBorhoodIdForProcess(int process_id) {
		int m_id = getMachineIdForProcessId(process_id);
		return problem.getNeighborhoodIdForMachineId(m_id);
	}

	public void exportToFileName(String filename) {
		try {
			FileWriter outFile = new FileWriter(filename);
			PrintWriter out = new PrintWriter(outFile);
			StringBuilder sb = new StringBuilder();
			for (int p = 0; p < P; p++) {
				sb.append(processes[p] + " ");
			}
			out.print(sb.toString().trim() + "\n");
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public List<Integer> getMachinesInSolution() {
		List<Integer> discreteMachinesInSolution = new ArrayList<Integer>();
		for (int m = 0; m < problem.M; m++) {
			for (int r = 0; r < problem.RE; r++) {
				if (usage[m][r] > 0) {
					discreteMachinesInSolution.add(m);
					break;
				}
			}
		}
		return discreteMachinesInSolution;
	}

	public void printDetails() {
		int[] all_machines = new int[problem.M];
		for (int i = 0; i < problem.M; i++) {
			all_machines[i] = i;
		}
		printDetails(all_machines);
	}

	public void printDetails(int[] selected_machines) {
		// StringBuilder sb = new StringBuilder();
		// int c = 0;
		// for (int p = 0; p < P; p++) {
		// if (processes[p] != -1) {
		// sb.append(String.format("p%d->m%d\t", p, processes[p]));
		// c++;
		// if (c % 10 == 0)
		// sb.append("\n");
		// }
		// }
		// System.out.println(sb.toString());

		for (int i = 0; i < selected_machines.length; i++) {
			int m = selected_machines[i];
			List<Integer> safety_capacities = new ArrayList<Integer>();
			int[] actual_capacities = new int[problem.RE];
			for (int r = 0; r < problem.RE; r++) {
				safety_capacities.add(problem.SC[m][r]);
				actual_capacities[r] = 0;
			}

			for (Integer p : getProcessesForMachine(m)) {
				for (int r = 0; r < problem.RE; r++) {
					actual_capacities[r] += problem.R[p][r];
				}
			}
			System.out.printf(
					"m%d safety_capacities=%s actual_load=%s --> p%s\n", m,
					safety_capacities, Arrays.toString(actual_capacities),
					getProcessesForMachine(m));
		}
	}

	public synchronized Solution copy() {
		// if(isLightCopy)
		// createFromLightCopy();
		Solution copiedSolution = new Solution(problem);
		for (int p = 0; p < P; p++) {
			copiedSolution.assignProcessToMachine(p, processes[p]);
		}
		copiedSolution.setCost(cost);
		if (costComponents != null)
			copiedSolution.setCostComponents(costComponents);
		return copiedSolution;
	}

	public synchronized Solution lightCopy() {
		Solution copiedSolution = new Solution(problem);
		copiedSolution.processes = processes.clone();
		copiedSolution.cost = cost;
		copiedSolution.costComponents = costComponents.clone();
		copiedSolution.isLightCopy = true;
		return copiedSolution;
	}

	public void createFromLightCopy() {
		for (int p = 0; p < P; p++) {
			assignProcessToMachine(p, processes[p]);
		}
		isLightCopy = false;
	}

	public void makeMoves(int[][] moves) {
		// new_assignments is a PX2 array new_assignments[i][0]=p_index and
		// new_assignments[i][1]=m_index
		for (int i = 0; i < moves.length; i++) {
			int p_index = moves[i][0];
			int m_index = moves[i][1];
			removeProcess(p_index);
			assignProcessToMachine(p_index, m_index);
		}
	}

	public int getSpreadForService(int s_id) {
		Set<Integer> aSet = new HashSet<Integer>();
		for (Integer p_index : problem.processesListForServices[s_id]) {
			int m_index = processes[p_index];
			aSet.add(problem.machines_locations[m_index]);
			System.out.printf("Process %d is in machine %d of location %d\n", p_index, m_index, problem.machines_locations[m_index]);
		}
		List<Integer> aList = new ArrayList<Integer>(aSet);
		Collections.sort(aList);
		System.out.println(aList);
		return aSet.size();
	}

	public boolean anotherProcessOfServiceExistsInSameLocation(Integer p_id) {
		int s_id = problem.getServiceIdForProcessId(p_id);
		int m_id = processes[p_id];
		int l_id = problem.getLocationIdForMachineId(m_id);
		for (Integer p_id2 : problem.getProcessesIdsForServiceId(s_id)) {
			if (p_id.intValue() != p_id2.intValue()) {
				int m_id2 = processes[p_id2];
				int l_id2 = problem.getLocationIdForMachineId(m_id2);
				if (l_id == l_id2) {
					// System.out.printf(
					// "p%d and p%d of s%d exist in same location %d\n",
					// p_id, p_id2, s_id, l_id);
					return true;
				}
			}
		}
		return false;
	}

	public boolean hasProcessFromServiceInLocation(int s_id, int l_id) {
		for (Integer p_id : problem.getProcessesIdsForServiceId(s_id)) {
			int m_id = processes[p_id];
			if (problem.getLocationIdForMachineId(m_id) == l_id)
				return true;
		}
		return false;
	}

	public void refreshLoadCostContribution() {
		for (int m_id = 0; m_id < problem.M; m_id++) {
			load_cost_contribution_zipped[m_id] = 0;
			for (int r_id = 0; r_id < problem.RE; r_id++) {
				long safe_capacity = problem.getSC(m_id, r_id);
				long v = 0;
				if (usage[m_id][r_id] > safe_capacity) {
					v = (usage[m_id][r_id] - safe_capacity)
							* problem.getResourceByIndex(r_id)
									.getWeightLoadCost();
					load_cost_contribution_zipped[m_id] += v;
				} else {
					v = 0;
					// v = (usage[m_id][r_id] - safe_capacity)
					// * problem.getResourceByIndex(r_id)
					// .getWeightLoadCost();
				}
				load_cost_contribution_unzipped[m_id][r_id] = v;
				if (v > load_cost_max_contribution_value){
					load_cost_max_contribution_value = v;
					load_cost_max_contribution_resource = r_id;
				}
			}
		}
	}

	public void refreshBalanceCostContribution() {
		balance_cost_contribution_unzipped = new long[M][R];
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int m = 0; m < problem.M; m++) {
				int amr1 = problem.C[m][bo.resourcea]
						- getUsageOfMachineForResource(m, bo.resourcea);
				int amr2 = problem.C[m][bo.resourceb]
						- getUsageOfMachineForResource(m, bo.resourceb);
				int delta = bo.target * amr1 - amr2;
				int max;
				if (delta > 0) {
					max = delta;
				} else {
					max = 0;
				}
				balance_cost_contribution_unzipped[m][bo.resourcea] += max
						* bo.weightBalanceCost;
				balance_cost_contribution_unzipped[m][bo.resourceb] += max
						* bo.weightBalanceCost;
			}
		}
		for (int m_id = 0; m_id < problem.M; m_id++) {
			balance_cost_contribution_zipped[m_id] = 0;
			for (int r_id = 0; r_id < problem.RE; r_id++) {
				balance_cost_contribution_zipped[m_id] += balance_cost_contribution_unzipped[m_id][r_id];
			}
			balance_cost_contribution_zipped[m_id] = balance_cost_contribution_zipped[m_id] / 2;

			if (balance_cost_contribution_zipped[m_id] > balance_cost_max_contribution_value)
				balance_cost_max_contribution_value = balance_cost_contribution_zipped[m_id];
		}
	}

	public void refreshLoadCostContribution(List<Integer> selectedMachines) {
		for (Integer m_obj: selectedMachines) {
			int m_id = m_obj.intValue();
			load_cost_contribution_zipped[m_id] = 0;
			for (int r_id = 0; r_id < problem.RE; r_id++) {
				long safe_capacity = problem.getSC(m_id, r_id);
				long v = 0;
				if (usage[m_id][r_id] > safe_capacity) {
					v = (usage[m_id][r_id] - safe_capacity)
							* problem.getResourceByIndex(r_id)
									.getWeightLoadCost();
					load_cost_contribution_zipped[m_id] += v;
				} else {
					v = 0;
					// v = (usage[m_id][r_id] - safe_capacity)
					// * problem.getResourceByIndex(r_id)
					// .getWeightLoadCost();
				}
				load_cost_contribution_unzipped[m_id][r_id] = v;
				if (v > load_cost_max_contribution_value){
					load_cost_max_contribution_value = v;
					load_cost_max_contribution_resource = r_id;
				}
			}
		}
	}
	
	public void refreshBalanceCostContribution(List<Integer> selectedMachines) {
		balance_cost_contribution_unzipped = new long[M][R];
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (Integer m_obj: selectedMachines) {
				int m = m_obj.intValue();
				int amr1 = problem.C[m][bo.resourcea]
						- getUsageOfMachineForResource(m, bo.resourcea);
				int amr2 = problem.C[m][bo.resourceb]
						- getUsageOfMachineForResource(m, bo.resourceb);
				int delta = bo.target * amr1 - amr2;
				int max;
				if (delta > 0) {
					max = delta;
				} else {
					max = 0;
				}
				balance_cost_contribution_unzipped[m][bo.resourcea] += max
						* bo.weightBalanceCost;
				balance_cost_contribution_unzipped[m][bo.resourceb] += max
						* bo.weightBalanceCost;
			}
		}
		for (Integer m_obj: selectedMachines) {
			int m_id = m_obj.intValue();
			balance_cost_contribution_zipped[m_id] = 0;
			for (int r_id = 0; r_id < problem.RE; r_id++) {
				balance_cost_contribution_zipped[m_id] += balance_cost_contribution_unzipped[m_id][r_id];
			}
			balance_cost_contribution_zipped[m_id] = balance_cost_contribution_zipped[m_id] / 2;

			if (balance_cost_contribution_zipped[m_id] > balance_cost_max_contribution_value)
				balance_cost_max_contribution_value = balance_cost_contribution_zipped[m_id];
		}
	}
	
	public void printTextileLoadCostContribution() {
		StringBuilder sb = new StringBuilder();
		sb.append("|_. Machines ");
		for (int r_id = 0; r_id < problem.RE; r_id++) {
			sb.append(String.format("|_. Resource%d", r_id));
		}
		sb.append("|_. Total|\n");
		for (int m_id = 0; m_id < problem.M; m_id++) {
			sb.append(String.format("|m%d\t", m_id));
			for (int r_id = 0; r_id < problem.RE; r_id++) {
				sb.append(String.format("|>. %,d",
						load_cost_contribution_unzipped[m_id][r_id]));
			}
			sb.append(String.format("|>. %,d|\n",
					load_cost_contribution_zipped[m_id]));
		}
		System.out.println(sb.toString());
	}

	public void printTextileBalanceCostContribution() {
		StringBuilder sb = new StringBuilder();
		sb.append("|_. Machines ");
		for (int r_id = 0; r_id < problem.RE; r_id++) {
			sb.append(String.format("|_. Resource%d", r_id));
		}
		sb.append("|_. Total|\n");
		for (int m_id = 0; m_id < problem.M; m_id++) {
			sb.append(String.format("|m%d\t", m_id));
			for (int r_id = 0; r_id < problem.RE; r_id++) {
				sb.append(String.format("|>. %,d",
						balance_cost_contribution_unzipped[m_id][r_id]));
			}
			sb.append(String.format("|>. %,d|\n",
					balance_cost_contribution_zipped[m_id]));
		}
		System.out.println(sb.toString());
	}

	private int getMaxResourceIdForMachine(int r_s_m) {
		long max = balance_cost_contribution_unzipped[r_s_m][0]
				+ load_cost_contribution_unzipped[r_s_m][0];
		int pmax = 0;
		for (int j = 1; j < problem.RE; j++) {
			if (balance_cost_contribution_unzipped[r_s_m][j]
					+ load_cost_contribution_unzipped[r_s_m][j] > max) {
				max = balance_cost_contribution_unzipped[r_s_m][j]
						+ load_cost_contribution_unzipped[r_s_m][j];
				pmax = j;
			}
		}
		return pmax;
	}

	public int findMaxContributingProcess(int r_s_m,
			List<Integer> bannedProcesses) {
		System.out.println("Banned processes " + bannedProcesses);
		int max_resource_id = getMaxResourceIdForMachine(r_s_m);
		long max_contrib = 0;
		int max_contrib_p = -1;
		for (int j = 0; j < problem.P; j++) {
			if (bannedProcesses.contains(new Integer(j)))
				continue;
			// if (machinesByProcesses[r_s_m][j] == 1) {
			if (processes[j] == r_s_m) {
				Process aProcess = problem.getProcesses().get(j);
				if (aProcess.getRequirementFor("r" + max_resource_id) > max_contrib) {
					max_contrib = aProcess.getRequirementFor("r"
							+ max_resource_id);
					max_contrib_p = j;
				}
			}
		}
		if (max_contrib_p == -1) {
			return max_contrib_p;
			// throw new IllegalStateException("NO PROCESSES LEFT!!");
		} else {
			System.out.printf(
					"Max contribution in machine m%d r%d by p%d with %,d\n",
					r_s_m, max_resource_id, max_contrib_p, max_contrib);
			return max_contrib_p;
		}
	}

	public boolean isSingleMoveFeasible(int p_id, int m_id) {
		int original_m_id = getMachineIdForProcessId(p_id);
		removeProcess(p_id);
		assignProcessToMachine(p_id, m_id);
		// capacity constraint
		boolean f1 = false;
		for (int r = 0; r < problem.RE; r++)
			if (usage[m_id][r] > problem.C[m_id][r]) {
				f1 = true;
			}
		if (f1) {
			removeProcess(p_id);
			assignProcessToMachine(p_id, original_m_id);
			return false;
		}

		// conflict constraint
		boolean f2 = false;
		int s_id = problem.getServiceIdForProcessId(p_id);
		Set<Integer> aSet = new HashSet<Integer>();
		for (Integer p_index : problem.processesListForServices[s_id]) {
			aSet.add(processes[p_index]);
		}
		if (aSet.size() < problem.processesListForServices[s_id].size()) {
			f2 = true;
		}
		if (f2) {
			removeProcess(p_id);
			assignProcessToMachine(p_id, original_m_id);
			return false;
		}

		// dependency constraint
		int original_n_id = problem
				.getNeighborhoodIdForMachineId(original_m_id);
		int n_id = problem.getNeighborhoodIdForMachineId(m_id);
		if (original_n_id != n_id) {
			removeProcess(p_id);
			assignProcessToMachine(p_id, original_m_id);
			return false;
		}
		removeProcess(p_id);
		assignProcessToMachine(p_id, original_m_id);
		return true;
	}

	// used by matching algorithm written by CV
	public long getLoadBalanceMachineProfitInCaseProcessMovesToMachine(
			int p_id, int m_id) {
		// load cost original
		long sum1_original = 0;
		for (int r = 0; r < problem.RE; r++) {
			int usage = getUsageOfMachineForResource(m_id, r);
			int safety_capacity = problem.SC[m_id][r];
			int delta = usage - safety_capacity;
			int max;
			if (delta > 0)
				max = delta;
			else
				max = 0;
			sum1_original = max
					* problem.getResourceByIndex(r).getWeightLoadCost();
		}

		// balance cost
		long sum2_original = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {

			int amr1 = problem.C[m_id][bo.resourcea]
					- getUsageOfMachineForResource(m_id, bo.resourcea);
			int amr2 = problem.C[m_id][bo.resourceb]
					- getUsageOfMachineForResource(m_id, bo.resourceb);
			int delta = bo.target * amr1 - amr2;
			int max;
			if (delta > 0) {
				max = delta;
			} else {
				max = 0;
			}
			sum2_original = max * bo.weightBalanceCost;
		}

		int original_m_id = getMachineIdForProcessId(p_id);
		removeProcess(p_id);
		assignProcessToMachine(p_id, m_id);

		// capacity constraint
		boolean f1 = false;
		for (int r = 0; r < problem.RE; r++)
			if (usage[m_id][r] > problem.C[m_id][r]) {
				f1 = true;
			}
		if (f1) {
			removeProcess(p_id);
			assignProcessToMachine(p_id, original_m_id);
			return Long.MIN_VALUE;
		}

		// conflict constraint
		boolean f2 = false;
		int s_id = problem.getServiceIdForProcessId(p_id);
		Set<Integer> aSet = new HashSet<Integer>();
		for (Integer p_index : problem.processesListForServices[s_id]) {
			aSet.add(processes[p_index]);
		}
		if (aSet.size() < problem.processesListForServices[s_id].size()) {
			f2 = true;
		}
		if (f2) {
			removeProcess(p_id);
			assignProcessToMachine(p_id, original_m_id);
			return Long.MIN_VALUE;
		}

		// dependency constraint
		int original_n_id = problem
				.getNeighborhoodIdForMachineId(original_m_id);
		int n_id = problem.getNeighborhoodIdForMachineId(m_id);
		if (original_n_id != n_id) {
			removeProcess(p_id);
			assignProcessToMachine(p_id, original_m_id);
			return Long.MIN_VALUE;
		}

		// load cost
		long sum1 = 0;
		for (int r = 0; r < problem.RE; r++) {
			int usage = getUsageOfMachineForResource(m_id, r);
			int safety_capacity = problem.SC[m_id][r];
			int delta = usage - safety_capacity;
			int max;
			if (delta > 0)
				max = delta;
			else
				max = 0;
			sum1 += max * problem.getResourceByIndex(r).getWeightLoadCost();
		}

		// balance cost
		long sum2 = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {

			int amr1 = problem.C[m_id][bo.resourcea]
					- getUsageOfMachineForResource(m_id, bo.resourcea);
			int amr2 = problem.C[m_id][bo.resourceb]
					- getUsageOfMachineForResource(m_id, bo.resourceb);
			int delta = bo.target * amr1 - amr2;
			int max;
			if (delta > 0) {
				max = delta;
			} else {
				max = 0;
			}
			sum2 += max * bo.weightBalanceCost;
		}

		removeProcess(p_id);
		assignProcessToMachine(p_id, original_m_id);
		// System.out.printf("%,d %,d %,d %,d\n", sum1, sum1_original, sum2,
		// sum2_original);
		return -(sum1 - sum1_original + sum2 - sum2_original);
	}

	public long getLoadBalanceProfitInCaseProcessMovesFromMachine(int p_id) {
		int m_id = getMachineIdForProcessId(p_id);

		// load cost
		long sum1_original = 0;
		for (int r = 0; r < problem.RE; r++) {
			int usage = getUsageOfMachineForResource(m_id, r);
			int safety_capacity = problem.SC[m_id][r];
			int delta = usage - safety_capacity;
			int max;
			if (delta > 0)
				max = delta;
			else
				max = 0;
			sum1_original = max
					* problem.getResourceByIndex(r).getWeightLoadCost();
		}

		// balance cost
		long sum2_original = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			int amr1 = problem.C[m_id][bo.resourcea]
					- getUsageOfMachineForResource(m_id, bo.resourcea);
			int amr2 = problem.C[m_id][bo.resourceb]
					- getUsageOfMachineForResource(m_id, bo.resourceb);
			int delta = bo.target * amr1 - amr2;
			int max;
			if (delta > 0) {
				max = delta;
			} else {
				max = 0;
			}
			sum2_original = max * bo.weightBalanceCost;
		}

		removeProcess(p_id);

		// load cost
		long sum1 = 0;
		for (int r = 0; r < problem.RE; r++) {
			int usage = getUsageOfMachineForResource(m_id, r);
			int safety_capacity = problem.SC[m_id][r];
			int delta = usage - safety_capacity;
			int max;
			if (delta > 0)
				max = delta;
			else
				max = 0;
			sum1 = max * problem.getResourceByIndex(r).getWeightLoadCost();
		}

		// balance cost
		long sum2 = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			int amr1 = problem.C[m_id][bo.resourcea]
					- getUsageOfMachineForResource(m_id, bo.resourcea);
			int amr2 = problem.C[m_id][bo.resourceb]
					- getUsageOfMachineForResource(m_id, bo.resourceb);
			int delta = bo.target * amr1 - amr2;
			int max;
			if (delta > 0) {
				max = delta;
			} else {
				max = 0;
			}
			sum2 = max * bo.weightBalanceCost;
		}
		assignProcessToMachine(p_id, m_id);
		// System.out.printf("%,d %,d %,d %,d\n", sum1, sum1_original, sum2,
		// sum2_original);
		return sum1_original - sum1 + sum2_original - sum2;
	}

	public boolean equals(Solution anotherSolution) {
		for (int p = 0; p < processes.length; p++) {
			if (processes[p] != anotherSolution.processes[p]) {
				return false;
			}
		}
		return true;
	}

	public void printSimilarityInfoWith(Solution anotherSolution) {
		boolean flag = true;
		StringBuilder sb = new StringBuilder();
		sb.append("Similarity Info: ");
		for (int p = 0; p < processes.length; p++) {
			if (processes[p] != anotherSolution.processes[p]) {
				sb.append(String.format("p%d->m%d!=m%d ", p, processes[p],
						anotherSolution.processes[p]));
				flag = false;
			}
		}
		if (flag)
			System.out.println("Identical solutions");
		else
			System.out.println(sb.toString());
	}

	public boolean isIdentical(Solution anotherSolution) {
		for (int p = 0; p < processes.length; p++) {
			if (processes[p] != anotherSolution.processes[p]) {
				return false;
			}
		}
		return true;
	}

	public long[] getCostComponents() {
		return costComponents;
	}

	public void setCostComponents(long[] c) {
		costComponents = c.clone();
	}

	public long getCost() {
		return cost;
	}

	public void setCost(long c) {
		cost = c;
	}

	public boolean areAllProcessesAssigned() {
		for (int i = 0; i < P; i++) {
			if (processes[i] == -1)
				return false;
		}
		return true;
	}

	public double percentageCompleted() {
		int count = 0;
		for (int i = 0; i < P; i++) {
			if (processes[i] != -1)
				count++;
		}
		return count / (double) P;
	}

	public long getCRC32Value() {
		CRC32 c = new CRC32();
		ObjectOutput out;
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			out = new ObjectOutputStream(bos);
			out.writeObject(this);
		} catch (IOException e) {
			e.printStackTrace();
		}
		c.update(bos.toByteArray());
		return c.getValue();
	}

	public List<Integer> getAssignedProvcessesInConflictWith(int pId, int mId) {
		// TODO Auto-generated method stub
		return null;
	}

	private void createServiceProcessesDistribution() {
		serviceProcessesPerLocation = new int[problem.S][problem.L + 1];
		serviceProcessesPerNeighborhood = new int[problem.S][problem.N + 1];

		for (int s_index = 0; s_index < problem.S; s_index++) {
			int count_locations = 0, count_neighb = 0;
			for (Integer p_index : problem.processesListForServices[s_index]) {
				int m_index = processes[p_index];

				int location = problem.machines_locations[m_index];
				if (serviceProcessesPerLocation[s_index][location] == 0)
					count_locations++;
				serviceProcessesPerLocation[s_index][location]++;

				int neighborhood = problem.machines_neighborhoods[m_index];
				if (serviceProcessesPerNeighborhood[s_index][neighborhood] == 0)
					count_neighb++;
				serviceProcessesPerNeighborhood[s_index][neighborhood]++;
			}
			serviceProcessesPerLocation[s_index][problem.L] = count_locations;
			serviceProcessesPerNeighborhood[s_index][problem.N] = count_neighb;
		}
	}

	private void createTransientResourceUsageOfMachine(Solution initial) {
		transientResourceUsageOfMachine = new long[problem.M][problem.TRE.length];
		int[] init_procs = initial.processes;

		// Calculate moved out process contribution
		for (int p = 0; p < processes.length; p++) {
			if (processes[p] != init_procs[p]) {
				for (int j = 0; j < problem.TRE.length; j++) {
					transientResourceUsageOfMachine[init_procs[p]][j] += problem
							.getR(p, problem.TRE[j]);
				}
			}
		}
	}

	private void updateServiceProcessesDistribution(int pId, int orig_mId,
			int mId) {
		int s_index = problem.getServiceIdForProcessId(pId);

		int orig_location = problem.machines_locations[orig_mId];
		int new_location = problem.machines_locations[mId];
		if (orig_location != new_location) {
			int count_change = 0;

			if (serviceProcessesPerLocation[s_index][orig_location] == 1)
				count_change--;
			serviceProcessesPerLocation[s_index][orig_location]--;

			if (serviceProcessesPerLocation[s_index][new_location] == 0)
				count_change++;
			serviceProcessesPerLocation[s_index][new_location]++;

			serviceProcessesPerLocation[s_index][problem.L] += count_change;
		}

		int orig_neighborhood = problem.machines_neighborhoods[orig_mId];
		int new_neighborhood = problem.machines_neighborhoods[mId];
		if (orig_neighborhood != new_neighborhood) {
			int count_change = 0;

			if (serviceProcessesPerNeighborhood[s_index][orig_neighborhood] == 1)
				count_change--;
			serviceProcessesPerNeighborhood[s_index][orig_neighborhood]--;

			if (serviceProcessesPerNeighborhood[s_index][new_neighborhood] == 0)
				count_change++;
			serviceProcessesPerNeighborhood[s_index][new_neighborhood]++;

			serviceProcessesPerNeighborhood[s_index][problem.N] += count_change;
		}
	}

	private void updateServiceProcessesDistributionAdd(int pId, int mId) {
		int s_index = problem.getServiceIdForProcessId(pId);
		int new_location = problem.machines_locations[mId];

		int count_change_s = 0;
		if (serviceProcessesPerLocation[s_index][new_location] == 0)
			count_change_s++;
		serviceProcessesPerLocation[s_index][new_location]++;
		serviceProcessesPerLocation[s_index][problem.L] += count_change_s;

		int new_neighborhood = problem.machines_neighborhoods[mId];
		int count_change_l = 0;

		if (serviceProcessesPerNeighborhood[s_index][new_neighborhood] == 0)
			count_change_l++;
		serviceProcessesPerNeighborhood[s_index][new_neighborhood]++;

		serviceProcessesPerNeighborhood[s_index][problem.N] += count_change_l;
	}

	public void updateServiceProcessesDistributionRemove(int pId, int orig_mId) {
		int s_index = problem.getServiceIdForProcessId(pId);

		int orig_location = problem.machines_locations[orig_mId];
		int count_change_l = 0;

		if (serviceProcessesPerLocation[s_index][orig_location] == 1)
			count_change_l--;
		serviceProcessesPerLocation[s_index][orig_location]--;
		serviceProcessesPerLocation[s_index][problem.L] += count_change_l;

		int orig_neighborhood = problem.machines_neighborhoods[orig_mId];
		int count_change_n = 0;

		if (serviceProcessesPerNeighborhood[s_index][orig_neighborhood] == 1)
			count_change_n--;
		serviceProcessesPerNeighborhood[s_index][orig_neighborhood]--;
		serviceProcessesPerNeighborhood[s_index][problem.N] += count_change_n;
	}

	public void updateTransientResourceUsageOfMachine(Solution initial,
			int pId, int orig_mId, int mId) {

		int[] init_procs = initial.processes;

		if (orig_mId != init_procs[pId]) {
			if (mId == init_procs[pId]) {
				for (int j = 0; j < problem.TRE.length; j++) {
					transientResourceUsageOfMachine[init_procs[pId]][j] -= problem
							.getR(pId, problem.TRE[j]);
				}
			}
		} else {
			if (mId != init_procs[pId]) {
				for (int j = 0; j < problem.TRE.length; j++) {
					transientResourceUsageOfMachine[init_procs[pId]][j] += problem
							.getR(pId, problem.TRE[j]);
				}
			}
		}
	}

	public SolverEnvironment getEnv() {
		return env;
	}

	public void setEnv(SolverEnvironment env) {
		if (this.env != env) {
			this.env = env;
			createTransientResourceUsageOfMachine(env.getInitialSolution());
		}
	}

}
